﻿using DocumentFormat.OpenXml.Lite.Core.ContentType;
using DocumentFormat.OpenXml.Lite.Core.DocumentFS;
using DocumentFormat.OpenXml.Lite.Core.Office;
using DocumentFormat.OpenXml.Lite.Core.Relationship;
using DocumentFormat.OpenXml.Lite.SpreadsheetML.Excel.Base;
using DocumentFormat.OpenXml.Lite.SpreadsheetML.Excel.Shared;
using T=DocumentFormat.OpenXml.Lite.SpreadsheetML.Excel.Table;
using DocumentFormat.OpenXml.Lite.SpreadsheetML.Excel.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DFS = DocumentFormat.OpenXml.Lite.Core.DocumentFS;
using DocumentFormat.OpenXml.Lite.SpreadsheetML.Excel.WorkbookElements;
using DocumentFormat.OpenXml.Lite.SpreadsheetML.Drawing;
using DocumentFormat.OpenXml.Lite.Helper;
using DocumentFormat.OpenXml.Lite.Shared;
using DocumentFormat.OpenXml.Lite.Core.Interfaces;
using System.Reflection;

namespace DocumentFormat.OpenXml.Lite.SpreadsheetML
{
    internal class SpreadsheetDocument : DocumentBase
    {
        private int charts = 0;
        private int images = 0;
        private int drawings = 0;
        private int tables = 0;
        public Workbook Workbook { get; private set; }

        public SpreadsheetDocument(Guid id, DocumentType type)
            : base(id, type)
        {
        }

        public override bool ExtractFile(string file, System.IO.Stream content)
        {
            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            if (file == @"[Content_Types].xml")
            {
                ContentTypeManager.Deserialize(content);
                return true;
            }
            else if (file == @"_rels\.rels" || file == @"_rels/.rels")
            {
                var relManager = sandbox.GetDirectory("_rels").GetFile(".rels").Object as RelationshipManager;
                relManager.Deserialize(content);
                return true;
            }
            else if (file == @"xl\workbook.xml" || file == @"xl/workbook.xml")
            {
                Workbook.Deserialize(content);
                return true;
            }
            else if (file == @"xl\sharedStrings.xml" || file == @"xl/sharedStrings.xml")
            {
                SharedStringTable sst = new SharedStringTable();
                sst.Deserialize(content);
                sst.DocumentId = Id;

                Workbook.SharedStringTable = sst;
                sandbox.CreateFile(file.Replace('\\', '/'), sst);
                return true;
            }
            else if (file == @"xl\styles.xml" || file == @"xl/styles.xml")
            {
                StyleSheet ss = new StyleSheet();
                ss.Deserialize(content);
                ss.DocumentId = Id;

                Workbook.StyleSheet = ss;
                sandbox.CreateFile(file.Replace('\\', '/'), ss);
                return true;
            }
            else if (file.StartsWith(@"xl\worksheets\sheet") || file.StartsWith(@"xl/worksheets/sheet"))
            {
                Worksheet sheet = new Worksheet();
                sheet.Deserialize(content);
                sheet.DocumentId = Id;

                if (Workbook.Worksheets == null)
                {
                    Workbook.Worksheets = new List<Worksheet>();
                }
                Workbook.Worksheets.Add(sheet);
                sandbox.CreateFile(file.Replace('\\', '/'), sheet);

                return true;
            }
            else if (file.StartsWith(@"xl\tables\") || file.StartsWith(@"xl/tables/"))
            {
                tables++;
                T.Table table = new T.Table();
                table.Deserialize(content);
                table.DocumentId = Id;

                if (Workbook.Tables == null)
                {
                    Workbook.Tables = new List<T.Table>();
                }
                Workbook.Tables.Add(table);
                sandbox.CreateFile(file.Replace('\\', '/'), table);

                return true;
            }
            else if (file.StartsWith(@"xl\drawings\drawing") || file.StartsWith(@"xl/drawings/drawing"))
            {
                drawings++;
                WorksheetDrawing wd = new WorksheetDrawing();
                wd.Deserialize(content);
                wd.DocumentId = Id;

                sandbox.CreateFile(file.Replace('\\', '/'), wd);

                return true;
            }
            else if (file.StartsWith(@"xl\charts\chart") || file.StartsWith(@"xl/charts/chart"))
            {
                charts++;
                System.Type t = System.Type.GetType("DocumentFormat.OpenXml.Lite.DrawingML.Helper, DocumentFormat.OpenXml.Lite.DrawingML");
                if(t != null)
                {
                    MethodInfo method = t.GetMethod("ExtractChart", BindingFlags.Static | BindingFlags.Public);
                    method.Invoke(null, new object[] { file, content, sandbox });

                    return true;
                }
            }
            else if (file.StartsWith(@"xl\theme\theme") || file.StartsWith(@"xl/theme/theme"))
            {
                System.Type t = System.Type.GetType("DocumentFormat.OpenXml.Lite.DrawingML.Helper, DocumentFormat.OpenXml.Lite.DrawingML");
                if (t != null)
                {
                    MethodInfo method = t.GetMethod("ExtractTheme", BindingFlags.Static | BindingFlags.Public);
                    method.Invoke(null, new object[] { file, content, sandbox });

                    return true;
                }
            }
            else if (file.StartsWith(@"xl\media\") || file.StartsWith(@"xl/media/"))
            {
                ImageType type = ImageType.None;
                string extension = file.Split('.')[1];
                switch (extension)
                {
                    case "bmp" :
                        type = ImageType.BMP;
                        break;
                    case "gif":
                        type = ImageType.GIF;
                        break;
                    case "jpeg":
                        type = ImageType.JPEG;
                        break;
                    case "jpg":
                        type = ImageType.JPG;
                        break;
                    case "png":
                        type = ImageType.PNG;
                        break;
                    case "tiff":
                        type = ImageType.TIFF;
                        break;
                    default: break;
                }

                if (type == ImageType.None)
                {
                    return false;
                }

                images++;
                Image img = new Image(null, type);
                img.Deserialize(content);

                sandbox.CreateFile(file.Replace('\\', '/'), img);

                return true;
            }
            if (file.EndsWith(@".rels"))
            {
                var relManager = new RelationshipManager();
                relManager.Deserialize(content);
                sandbox.CreateFile(file.Replace('\\', '/'), relManager);
                return true;
            }

            return base.ExtractFile(file, content);
        }

        public override T FindElementFor<T>(object obj, string relationshipId)
        {
            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            DFS.File file = sandbox.GetFilesRecursive().FirstOrDefault(f => f.Object == obj);
            if (file == null)
            {
                throw new NullReferenceException("Cannot find object!");
            }
            file = sandbox.GetFilesRecursive().FirstOrDefault(f => f.Name == file.Name + ".rels");
            if (file == null)
            {
                throw new NullReferenceException("Cannot find object!");
            }
            var target = (file.Object as RelationshipManager).RelationshipTags
                .FirstOrDefault(r => r.Id == relationshipId).Target;
            if (target == null || target == string.Empty)
            {
                throw new NullReferenceException("Cannot find object!");
            }
            var o = sandbox.GetFile(DFS.Directory.Combine(file.Parent.Parent, target)).Object;
            if (!(o is T))
            {
                throw new ArgumentException("Invalid object type: " + typeof(T).ToString() + ".");
            }
            return (T)o;
        }

        public override void CreateDefault()
        {
            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);

            DFS.Directory relDir = sandbox.CreateDirectory("_rels");
            DFS.Directory wordDir = sandbox.CreateDirectory("xl");

            Workbook = new Workbook(this);
            File documentXml = wordDir.CreateFile("workbook.xml", Workbook);

            RelationshipManager relManager = new RelationshipManager();
            File relFile = relDir.CreateFile(".rels", relManager);
            relManager.CreateRelationship(RelationshipURI.OfficeDocument, documentXml);

            RelationshipManagers.Add(relManager);

            ContentTypeManager = new ContentTypeManager();
            File contFile = sandbox.CreateFile("[Content_Types].xml", ContentTypeManager);

            ContentTypeManager.CreateDefault(ContentType.Xml, ExtensionType.Xml);
            ContentTypeManager.CreateDefault(ContentType.Rels, ExtensionType.Rels);

            ContentTypeManager.CreateOverride(ContentType.Spreadsheet, documentXml);
        }

        public Worksheet CreateWorksheet(string name, Nullable<ST_SheetState> sheetState)
        {
            Worksheet sheet = new Worksheet(name);
            sheet.DocumentId = this.Id;

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            if (!sandbox.ExistsDirectory("xl/worksheets"))
            {
                sandbox.CreateDirectory("xl/worksheets");
            }
            DFS.Directory dir = sandbox.GetDirectory("xl/worksheets");
            uint sheets = (uint)dir.GetFiles().Count + 1;

            File sheetFile = dir.CreateFile("sheet" + sheets.ToString() + ".xml", sheet);
            this.ContentTypeManager.CreateOverride(ContentType.Worksheet, sheetFile);

            if (!sandbox.ExistsDirectory("xl/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl"), "workbook.xml.rels");
            }

            File relFile = sandbox.GetFile("xl/_rels/workbook.xml.rels");
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            int relId = relManager.CreateRelationship(
                RelationshipURI.Worksheet, sheetFile, sandbox.GetDirectory("xl"));

            Workbook.AddNamespace("r", Constants.Relationship);
            Workbook.AddSheet(name, sheetState, sheets, RelationshipManager.GetRelationshipId(relId));

            return sheet;
        }

        public SharedStringTable CreateSharedStringTable()
        {
            SharedStringTable table = new SharedStringTable();
            table.DocumentId = this.Id;

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            File tableFile = sandbox.CreateFile("xl/sharedStrings.xml", table);
            this.ContentTypeManager.CreateOverride(ContentType.SharedStrings, tableFile);

            if (!sandbox.ExistsDirectory("xl/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl"), "workbook.xml.rels");
            }

            File relFile = sandbox.GetFile("xl/_rels/workbook.xml.rels");
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            relManager.CreateRelationship(RelationshipURI.SharedStrings, tableFile, sandbox.GetDirectory("xl"));

            return table;
        }

        public T.Table CreateTable(Worksheet sheet)
        {
            T.Table table = new T.Table();
            table.DocumentId = this.Id;

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            DFS.Directory tableDir = sandbox.CreateDirectory("xl/tables");
            File tableFile = tableDir.CreateFile("table" + (++tables).ToString() + ".xml", table);
            this.ContentTypeManager.CreateOverride(ContentType.SmlTable, tableFile);
            
            DFS.Directory sheetDir = sandbox.GetDirectory("xl/worksheets");
            File file = sheetDir.GetFiles().FirstOrDefault(f => f.Object == sheet);
            if (!sheetDir.ExistsDirectory("_rels"))
            {
                CreateRelationsipFile(sheetDir, file.Name + ".rels");
            }

            File relFile = sheetDir.GetFile("_rels/" + file.Name + ".rels");
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            int relId = relManager.CreateRelationship(RelationshipURI.SmlTable, tableFile, sheetDir);

            table.Id = (uint)tables;
            table.Name = "table" + tables.ToString();
            table.RelationshipId = RelationshipManager.GetRelationshipId(relId);

            sheet.AddTable(table.RelationshipId);

            return table;
        }

        public StyleSheet CreateStyleSheet()
        {
            StyleSheet styles = new StyleSheet();
            styles.DocumentId = this.Id;

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            File styleFile = sandbox.CreateFile("xl/style.xml", styles);
            this.ContentTypeManager.CreateOverride(ContentType.SmlStyle, styleFile);

            if (!sandbox.ExistsDirectory("xl/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl"), "workbook.xml.rels");
            }

            File relFile = sandbox.GetFile("xl/_rels/workbook.xml.rels");
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            relManager.CreateRelationship(RelationshipURI.Style, styleFile, sandbox.GetDirectory("xl"));

            return styles;
        }

        public WorksheetDrawing CreateDrawingElement(Worksheet sheet)
        {
            WorksheetDrawing wd = new WorksheetDrawing();
            wd.DocumentId = this.Id;

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            if (!sandbox.ExistsDirectory("xl/drawings"))
            {
                sandbox.CreateDirectory("xl/drawings");
            }
            File wdFile = sandbox.CreateFile("xl/drawings/drawing" + (++drawings) + ".xml", wd);
            this.ContentTypeManager.CreateOverride(ContentType.OfficeDrawing, wdFile);

            string relFileName = sandbox.GetDirectory("xl/worksheets")
                .GetFiles().FirstOrDefault(f => f.Object == sheet).Name + ".rels";
            if (!sandbox.ExistsDirectory("xl/worksheets/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl/worksheets"), relFileName);
            }
            else if (!sandbox.ExistsFile("xl/worksheets/_rels/" + relFileName))
            {
                RelationshipManager rm = new RelationshipManager();
                sandbox.CreateFile("xl/worksheets/_rels/" + relFileName, rm);
            }

            File relFile = sandbox.GetFile("xl/worksheets/_rels/" + relFileName);
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            wd.RelationshipId = RelationshipManager.GetRelationshipId(relManager.CreateRelationship(
                RelationshipURI.OfficeDrawing, wdFile, sandbox.GetDirectory("xl/worksheets")));

            return wd;
        }

        public string CreateChartElementFor(WorksheetDrawing obj, IDrawingElement chart)
        {
            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            if (!sandbox.ExistsDirectory("xl/charts"))
            {
                sandbox.CreateDirectory("xl/charts");
            }
            File chartFile = sandbox.CreateFile("xl/charts/chart" + (++charts) + ".xml", chart);
            this.ContentTypeManager.CreateOverride(ContentType.Chart, chartFile);

            string relFileName = sandbox.GetDirectory("xl/drawings").
                GetFiles().FirstOrDefault(f => f.Object == obj).Name + ".rels";
            if (!sandbox.ExistsDirectory("xl/drawings/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl/drawings"), relFileName);
            }
            else if (!sandbox.GetDirectory("xl/drawings/_rels").ExistsFile(relFileName))
            {
                RelationshipManager rm = new RelationshipManager();
                sandbox.CreateFile("xl/drawings/_rels/" + relFileName, rm);
            }

            File relFile = sandbox.GetFile("xl/drawings/_rels/" + relFileName);
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            int id = relManager.CreateRelationship(RelationshipURI.Chart, 
                chartFile, sandbox.GetDirectory("xl/drawings"));

            return RelationshipManager.GetRelationshipId(id);
        }

        public string CreatePictureElementFor(WorksheetDrawing obj, Image image)
        {
            this.ContentTypeManager.CreateDefaultForImage(image);

            DFS.Directory sandbox = DFS.DocumentFS.GetSandbox(Id);
            if (!sandbox.ExistsDirectory("xl/media"))
            {
                sandbox.CreateDirectory("xl/media");
            }
            File picFile = sandbox.CreateFile("xl/media/image" + 
                (++images) + "." + image.Type.GetDescription(), image);

            string relFileName = sandbox.GetDirectory("xl/drawings").
                GetFiles().FirstOrDefault(f => f.Object == obj).Name + ".rels";
            if (!sandbox.ExistsDirectory("xl/drawings/_rels"))
            {
                CreateRelationsipFile(sandbox.GetDirectory("xl/drawings"), relFileName);
            }
            else if (!sandbox.GetDirectory("xl/drawings/_rels").ExistsFile(relFileName))
            {
                RelationshipManager rm = new RelationshipManager();
                sandbox.CreateFile("xl/drawings/_rels/" + relFileName + ".rels", rm);
            }

            File relFile = sandbox.GetFile("xl/drawings/_rels/" + relFileName);
            RelationshipManager relManager = relFile.Object as RelationshipManager;
            int id = relManager.CreateRelationship(RelationshipURI.Image, 
                picFile, sandbox.GetDirectory("xl/drawings"));

            return RelationshipManager.GetRelationshipId(id);
        }

        private void CreateRelationsipFile(DFS.Directory dir, string fileName)
        {
            RelationshipManager relManager = new RelationshipManager();
            RelationshipManagers.Add(relManager);

            Directory relDir = dir.CreateDirectory("_rels");
            File relFile = relDir.CreateFile(fileName, relManager);
        }
    }
}
